package com.eh.utils;

import android.os.Parcel;
import android.os.Parcelable;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by zhangxiaowei on 17/4/18.
 */
public abstract class ParcelableUtil implements Parcelable {

    public static final Creator<ParcelableUtil> CREATOR = new Creator<ParcelableUtil>() {
        public ParcelableUtil createFromParcel(Parcel in) {
            Class<?> parceledClass;
            try {
                parceledClass = Class.forName(in.readString());
                ParcelableUtil model = (ParcelableUtil) parceledClass.newInstance();
                readData(model, in);
                return model;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        public ParcelableUtil[] newArray(int size) {
            return new ParcelableUtil[size];
        }
    };
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.getClass().getName());
        try {
            writeData(this, dest);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public int describeContents() {
        return 0;
    }

    protected static void writeData(ParcelableUtil model, Parcel out) throws Exception {
        Field[] fields = model.getClass().getDeclaredFields();
        Arrays.sort(fields, compareMemberByName);
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getType().isPrimitive()) {
                if (field.getType().equals(int.class)) {
                    if (field.getType().isArray()) {
                        int[] size = (int[]) field.get(model);
                        if (size == null) {
                            out.writeInt(0);
                            continue;
                        }
                        out.writeInt(size.length);
                        out.writeIntArray(size);
                    } else {
                        out.writeInt(field.getInt(model));
                    }

                } else if (field.getType().equals(double.class)) {
                    if (field.getType().isArray()) {
                        double[] size = (double[]) field.get(model);
                        if (size == null) {
                            out.writeInt(0);
                            continue;
                        }
                        out.writeInt(size.length);
                        out.writeDoubleArray(size);
                    } else {
                        out.writeDouble(field.getDouble(model));
                    }
                } else if (field.getType().equals(float.class)) {
                    if (field.getType().isArray()) {
                        float[] size = (float[]) field.get(model);
                        if (size == null) {
                            out.writeInt(0);
                            continue;
                        }
                        out.writeInt(size.length);
                        out.writeFloatArray(size);
                    } else {
                        out.writeFloat(field.getFloat(model));
                    }
                } else if (field.getType().equals(long.class)) {
                    if (field.getType().isArray()) {
                        long[] size = (long[]) field.get(model);
                        if (size == null) {
                            out.writeInt(0);
                            continue;
                        }
                        out.writeInt(size.length);
                        out.writeLongArray(size);
                    } else {
                        out.writeLong(field.getLong(model));
                    }

                } else if (field.getType().equals(boolean.class)) {
                    if (field.getType().isArray()) {
                        byte[] size = (byte[]) field.get(model);
                        if (size == null) {
                            out.writeInt(0);
                            continue;
                        }
                        out.writeInt(size.length);
                        out.writeByteArray(size);
                    } else {
                        out.writeByte(field.getBoolean(model) ? (byte) 1 : (byte) 0);
                    }
                }
            } else if (field.getType().equals(String.class)) {
                if (field.getType().isArray()) {
                    String[] size = (String[]) field.get(model);
                    if (size == null) {
                        out.writeInt(0);
                        continue;
                    }
                    out.writeInt(size.length);
                    out.writeStringArray(size);
                } else {
                    out.writeString((String) field.get(model));
                }
            } else if (field.getType().equals(Date.class)) {
                Date date = (Date) field.get(model);
                if (date != null) {
                    out.writeLong(date.getTime());
                } else {
                    out.writeLong(0);
                }
            } else if (field.getType().isAssignableFrom(List.class)) {
                List list = (List) field.get(model);

                ParameterizedType tp = (ParameterizedType) field.getGenericType();
                Type type = tp.getActualTypeArguments()[0];
                if (type instanceof ParameterizedType) {
                    if (list == null || list.size() == 0) {
                        out.writeInt(0);
                        continue;
                    }
                    int size = list.size();
                    out.writeInt(size);
                    for (int i = 0; i < size; i++) {
                        out.writeMap((Map) list.get(i));
                    }
                } else {
                    out.writeList(list);
                }


            } else if (field.getType().isAssignableFrom(Map.class)) {

                Map map0 = (Map) field.get(model);

                ParameterizedType tp = (ParameterizedType) field.getGenericType();
                Type type = tp.getActualTypeArguments()[1];

                if (type instanceof ParameterizedType) {
                    if (map0 == null || map0.size() == 0) {
                        out.writeInt(0);
                        continue;
                    }
                    Class clff = getClass(type.toString());
                    if (clff.isAssignableFrom(Map.class)) {
                        Map<String, Map> data = (Map) field.get(model);
                        out.writeInt(data.size());
                        for (Map.Entry<String, Map> enty : data.entrySet()) {
                            out.writeString(enty.getKey());
                            out.writeMap(enty.getValue());
                        }
                    } else {
                        Map<String, List> data = (Map) field.get(model);
                        out.writeInt(data.size());
                        for (Map.Entry<String, List> enty : data.entrySet()) {
                            out.writeString(enty.getKey());
                            out.writeList(enty.getValue());
                        }
                    }
                } else {
                    out.writeMap((Map) field.get(model));
                }
            } else if (ParcelableUtil.class.isAssignableFrom(field.getType())) {
                if (field.getType().isInterface()) {
                    continue;
                }
                out.writeParcelable((ParcelableUtil) field.get(model), 0);
            } else {

            }
        }
    }
    protected static void readData(ParcelableUtil model, Parcel in) throws Exception {
        Field[] fields = model.getClass().getDeclaredFields();
        Arrays.sort(fields, compareMemberByName);
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.getType().isPrimitive()) {
                if (field.getType().equals(int.class)) {
                    if (field.getType().isArray()) {
                        int size = in.readInt();
                        int[] data = new int[size];
                        in.readIntArray(data);
                        field.set(model, data);
                    } else {
                        field.set(model, in.readInt());
                    }
                } else if (field.getType().equals(double.class)) {
                    if (field.getType().isArray()) {
                        int size = in.readInt();
                        double[] data = new double[size];
                        in.readDoubleArray(data);
                        field.set(model, data);
                    } else {
                        field.set(model, in.readDouble());
                    }
                } else if (field.getType().equals(float.class)) {
                    if (field.getType().isArray()) {
                        int size = in.readInt();
                        float[] data = new float[size];
                        in.readFloatArray(data);
                        field.set(model, data);
                    } else {
                        field.set(model, in.readFloat());
                    }
                } else if (field.getType().equals(long.class)) {
                    if (field.getType().isArray()) {
                        int size = in.readInt();
                        long[] data = new long[size];
                        in.readLongArray(data);
                        field.set(model, data);
                    } else {
                        field.set(model, in.readLong());
                    }
                } else if (field.getType().equals(boolean.class)) {
                    if (field.getType().isArray()) {
                        int size = in.readInt();
                        byte[] data = new byte[size];
                        in.readByteArray(data);
                        field.set(model, data);
                    } else {
                        field.set(model, in.readByte() == 1);
                    }
                }
            } else if (field.getType().equals(String.class)) {
                if (field.getType().isArray()) {
                    int size = in.readInt();
                    String[] data = new String[size];
                    in.readStringArray(data);
                    field.set(model, data);
                } else {
                    field.set(model, in.readString());
                }
            } else if (field.getType().equals(Date.class)) {
                Date date = new Date(in.readLong());
                field.set(model, date);
            } else if (field.getType().isAssignableFrom(List.class)) {

                List list = new ArrayList();
                ParameterizedType tp = (ParameterizedType) field.getGenericType();
                Type type = tp.getActualTypeArguments()[0];
                if (type instanceof ParameterizedType) {
                    int size = in.readInt();
                    if (size <= 0) {
                        continue;
                    }
                    Class classs = getClass(type.toString());
                    ParameterizedType child = (ParameterizedType) type;
                    Class clazz = (Class) child.getActualTypeArguments()[1];
                    for (int i = 0; i < size; i++) {
                        Map map = null;
                        if (classs.isAssignableFrom(HashMap.class) || classs.getName().equals(Map.class.getName())) {
                            map = new HashMap();
                        } else {
                            map = (Map) field.getType().newInstance();
                        }
                        in.readMap(map, clazz.getClassLoader());
                        list.add(map);
                    }
                } else {
                    Class clazz = (Class) type;
                    in.readList(list, clazz.getClassLoader());
                }
                field.set(model, list);


            } else if (field.getType().isAssignableFrom(Map.class)) {

                Map map = null;
                if (field.getType().isAssignableFrom(HashMap.class) || field.getType().getName().equals(Map.class.getName())) {
                    map = new HashMap();
                } else {
                    map = (Map) field.getType().newInstance();
                }
                ParameterizedType tp = (ParameterizedType) field.getGenericType();
                Type type = tp.getActualTypeArguments()[1];
                if (type instanceof ParameterizedType) {
                    int size = in.readInt();
                    if (size <= 0) {
                        continue;
                    }
                    String strclazz = getClassString(type.toString());
                    Class aClass = Class.forName(strclazz);
                    ParameterizedType type11 = (ParameterizedType) type;
                    for (int i = 0; i < size; i++) {
                        if (aClass.isAssignableFrom(Map.class)) {
                            Class mapclaz = (Class) type11.getActualTypeArguments()[1];
                            Map map1 = null;
                            if (aClass.isAssignableFrom(HashMap.class) || strclazz.equals(Map.class.getName())) {
                                map1 = new HashMap();
                            } else {
                                map1 = (Map) aClass.newInstance();
                            }
                            String key = in.readString();
                            in.readMap(map1, mapclaz.getClassLoader());
                            map.put(key, map1);
                        } else {
                            String key = in.readString();
                            List list = null;
                            if (aClass.isAssignableFrom(ArrayList.class) || strclazz.equals(List.class.getName())) {
                                list = new ArrayList();
                            } else {
                                list = (List) aClass.newInstance();
                            }
                            Class mapcl = (Class) type11.getActualTypeArguments()[0];
                            in.readList(list, mapcl.getClassLoader());
                            map.put(key, list);
                        }
                    }

                } else {
                    Class clazz = (Class) type;
                    in.readMap(map, clazz.getClassLoader());
                }
                field.set(model, map);


            } else if (ParcelableUtil.class.isAssignableFrom(field.getType())) {
                if (field.getType().isInterface()) {
                    continue;
                }
                Class clazz = getClass(field.getType().toString());
                if (clazz != null) {

                    field.set(model, in.readParcelable(clazz.getClassLoader()));
                }
            } else {
            }
        }
    }

    private static String getClassString(String stClaz) {
        stClaz = stClaz.replace(" ", "");
        if (stClaz.startsWith("class") || stClaz.startsWith("Class") || stClaz.startsWith("interface")) {
            stClaz = stClaz.substring(5, stClaz.length());
        }
        if (stClaz.contains("<") || stClaz.contains(",")) {
            int first = stClaz.indexOf("<");
            stClaz = stClaz.substring(0, first);
        }
        return stClaz;
    }

    private static Class getClass(String stClaz) {
        Class clazz = null;
        try {
            clazz = (Class) Class.forName(getClassString(stClaz));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return clazz;
    }

    /*
     * 成员、字段和方法的比较器对象
     */
    private static Comparator<Field> compareMemberByName =
            new CompareMemberByName();

    private static class CompareMemberByName implements Comparator {
        public int compare(Object o1, Object o2) {
            String s1 = ((Member) o1).getName();
            String s2 = ((Member) o2).getName();

            if (o1 instanceof Method) {
                s1 += getSignature((Method) o1);
                s2 += getSignature((Method) o2);
            } else if (o1 instanceof Constructor) {
                s1 += getSignature((Constructor) o1);
                s2 += getSignature((Constructor) o2);
            }
            return s1.compareTo(s2);
        }
    }


    /**
     * 计算类的JVM签名
     */
    private static String getSignature(Class clazz) {
        String type = null;
        if (clazz.isArray()) {
            Class cl = clazz;
            int dimensions = 0;
            while (cl.isArray()) {
                dimensions++;
                cl = cl.getComponentType();
            }
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < dimensions; i++) {
                sb.append("[");
            }
            sb.append(getSignature(cl));
            type = sb.toString();
        } else if (clazz.isPrimitive()) {
            if (clazz == Integer.TYPE) {
                type = "I";
            } else if (clazz == Byte.TYPE) {
                type = "B";
            } else if (clazz == Long.TYPE) {
                type = "J";
            } else if (clazz == Float.TYPE) {
                type = "F";
            } else if (clazz == Double.TYPE) {
                type = "D";
            } else if (clazz == Short.TYPE) {
                type = "S";
            } else if (clazz == Character.TYPE) {
                type = "C";
            } else if (clazz == Boolean.TYPE) {
                type = "Z";
            } else if (clazz == Void.TYPE) {
                type = "V";
            }
        } else {
            type = "L" + clazz.getName().replace('.', '/') + ";";
        }
        return type;
    }


    /*
     * 计算JVM方法描述符。
     */
    private static String getSignature(Method meth) {
        StringBuffer sb = new StringBuffer();

        sb.append("(");

        Class[] params = meth.getParameterTypes(); // avoid clone
        for (int j = 0; j < params.length; j++) {
            sb.append(getSignature(params[j]));
        }
        sb.append(")");
        sb.append(getSignature(meth.getReturnType()));
        return sb.toString();
    }

    /*
     * 计算构造函数构造函数
     */
    private static String getSignature(Constructor cons) {
        StringBuffer sb = new StringBuffer();

        sb.append("(");

        Class[] params = cons.getParameterTypes();
        for (int j = 0; j < params.length; j++) {
            sb.append(getSignature(params[j]));
        }
        sb.append(")V");
        return sb.toString();
    }
}